package cn.edu.lcu.cs.ssm.aop;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 一个切面的示例，在方法执行前后输出某些信息，及执行的消耗时间。
 * 
 * @author Ling
 *
 */
@Aspect
@Component
public class MyLogAspect {

	/**
	 * <li>此方法仅用于标识切入点表达式，没有别的实际意义，方法为空</li>
	 * <li>一个Aspect中可以定义多个类似带标注的方法，标记多个表达式</li>
	 * <li>表达式语法：execution(<修饰符>？<返回类型> <声明类型>？<方法名>（<参数>）<异常>？)</li>
	 */
	// 本表达式的意义：dao包中的所有类的所有方法的执行
	@Pointcut("execution (* cn.edu.lcu.cs.ssm.dao.*.*(..))")
	private void pointcut() {
	}

	/**
	 * AfterThrowing通知，当被监测的方法抛出异常时激活
	 * 
	 * @param joinPoint
	 * @param ex SpringAOP注入的异常对象
	 */
	@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
	public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
		System.err.println("---------------------------------------------------------------------");
		System.err.println("异常通知\t" + joinPoint.getSignature() + "\t时间戳\t" + Instant.now()); // 输出被拦截方法的签名
		System.err.println("错误描述\t" + ex.getMessage());
		System.err.println("错误类型\t" + ex.getClass());
//		ex.printStackTrace();
	}

	/**
	 * 前置通知
	 * 
	 * @param joinPoint 连接点
	 */
//	@Before("execution (* cn.edu.lcu.cs.ssm.dao.*.*(..))") // 可以直接指定切入点表达式
//	@Before("pointcut()") // 也可以引用已定义的切入点表达式
	public void before(JoinPoint joinPoint) {
		System.err.println("---------------------------------------------------------------------");
		System.err.println("前置通知\t" + LocalDateTime.now() + "\t" + joinPoint.getSignature());
	}

	/**
	 * 环绕通知
	 * 
	 * @param joinPoint
	 * @param id        通过表达式中的 args(id) 注入
	 * @return
	 */
	// 复杂表达式：两个子表达式必须都满足
	// pointcut() dao包所有类的所有方法
	// args(id) 方法仅包含一个名字为id的参数；再根据被标注的方法签名推断，参数id必须是Long类型
	@Around("pointcut() && args(id)")
	public Object around(ProceedingJoinPoint joinPoint, Long id) {
		// 用err输出，仅仅是为了突出显示红色字体
		System.err.println("---------------------------------------------------------------------");
		System.err.println("注入的参数\tid\t" + id);
		Instant begin = Instant.now(); // 开始时间戳
		System.err.println("环绕通知\t" + joinPoint.getSignature() + "\t开始时间戳\t" + begin); // 输出被拦截方法的签名
		Object result = null;
		try {
			result = joinPoint.proceed(); // 调用被拦截的方法，并保留返回值
		} catch (Throwable e) {
			e.printStackTrace();
		}
		Instant end = Instant.now(); // 结束时间戳
		System.err.println("环绕通知\t" + joinPoint.getSignature() + "\t结束时间戳\t" + end); // 输出被拦截方法的签名

		System.err.printf("消耗了\t%d\t纳秒\n", Duration.between(begin, end).getNano());
		System.err.println("---------------------------------------------------------------------");
		return result;
	}

	/**
	 * 后置通知
	 * 
	 * @param joinPoint
	 */
//	@After("pointcut()")			// 后置通知。连接点退出的时候执行的通知（不论正常返回还是异常退出）
//	@AfterReturning("pointcut()")	// 返回后通知。连接点正常完成后执行的通知，不包括抛出异常的情况 
	public void after(JoinPoint joinPoint) {
		System.err.println("后置通知\t" + LocalDateTime.now() + "\t" + joinPoint.getSignature());
		System.err.println("---------------------------------------------------------------------");

	}

	/**
	 * 环绕通知
	 */
	// 复杂表达式：两个子表达式必须都满足
	// pointcut() dao包所有类的所有方法
	// args(id, username)
	// 方法包含id与username参数；再根据被标注的方法签名推断，参数id是Long类型，username是String类型
//	@Around("pointcut() && args(id, username)")
	public Object around(ProceedingJoinPoint joinPoint, Long id, String username) {
		System.err.println(id);
		System.err.println(username);
		return null; // 代码略，参考 around(ProceedingJoinPoint joinPoint, Long id)
	}

	/**
	 * 环绕通知
	 * 
	 */
	// 复杂表达式：两个子表达式必须都满足
	// pointcut() dao包所有类的所有方法
	// args(id) 方法仅包含一个名字为id的参数；再根据被标注的方法签名推断，参数id必须是String类型
//	@Around("pointcut() && args(id)")
	public Object around(ProceedingJoinPoint joinPoint, String id) {
		System.err.println(id);
		return null; // 代码略，参考 around(ProceedingJoinPoint joinPoint, Long id)
	}
}
